Verken krachtige TypeScript-alternatieven voor enums: const assertions en union types. Leer wanneer u elk kunt gebruiken voor robuuste, onderhoudbare code.
Voorbij Enums: TypeScript Const Assertions vs. Union Types
In de wereld van statisch getypeerde JavaScript met TypeScript zijn enums al lange tijd de favoriete methode voor het representeren van een vaste set benoemde constanten. Ze bieden een duidelijke en leesbare manier om een verzameling gerelateerde waarden te definiƫren. Naarmate projecten groeien en evolueren, zoeken ontwikkelaars echter vaak naar flexibelere en soms performantere alternatieven. Twee krachtige kanshebbers die regelmatig opduiken, zijn const assertions en union types. Dit artikel duikt in de nuances van het gebruik van deze alternatieven voor traditionele enums, biedt praktische voorbeelden en begeleidt u bij de keuze tussen de twee.
Begrip van Traditionele TypeScript Enums
Voordat we de alternatieven verkennen, is het essentieel om een stevige greep te krijgen op hoe standaard TypeScript-enums werken. Enums stellen u in staat om een set benoemde numerieke of stringconstanten te definiƫren. Ze kunnen numeriek (standaard) of op basis van strings zijn.
Numerieke Enums
Standaard worden enumleden toegewezen aan numerieke waarden beginnend bij 0.
enum DirectionNumeric {
Up,
Down,
Left,
Right
}
let myDirection: DirectionNumeric = DirectionNumeric.Up;
console.log(myDirection); // Output: 0
U kunt ook expliciet numerieke waarden toewijzen.
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500
}
let responseStatus: StatusCode = StatusCode.Success;
console.log(responseStatus); // Output: 200
String Enums
String enums worden vaak geprefereerd vanwege hun verbeterde debug-ervaring, aangezien de ledennamen behouden blijven in de gecompileerde JavaScript.
enum ColorString {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
let favoriteColor: ColorString = ColorString.Blue;
console.log(favoriteColor); // Output: "BLUE"
De Overhead van Enums
Hoewel enums handig zijn, komen ze met een lichte overhead. Wanneer gecompileerd naar JavaScript, worden TypeScript-enums omgezet in objecten die vaak omgekeerde mappings hebben (bijvoorbeeld het koppelen van de numerieke waarde terug naar de enumnaam). Dit kan nuttig zijn, maar draagt ook bij aan de bundelgrootte en is mogelijk niet altijd nodig.
Beschouw deze eenvoudige string enum:
enum Status {
Pending = "PENDING",
Processing = "PROCESSING",
Completed = "COMPLETED"
}
In JavaScript kan dit er ongeveer zo uitzien:
var Status;
(function (Status) {
Status["Pending"] = "PENDING";
Status["Processing"] = "PROCESSING";
Status["Completed"] = "COMPLETED";
})(Status || (Status = {}));
Voor eenvoudige, alleen-lezen sets van constanten kan deze gegenereerde code een beetje overdreven aanvoelen.
Alternatief 1: Const Assertions
Const assertions zijn een krachtige TypeScript-functie die u in staat stelt de compiler te vertellen om het meest specifieke type te infereren voor een waarde. Wanneer gebruikt met arrays of objecten die bedoeld zijn om een vaste set waarden te representeren, kunnen ze dienen als een lichtgewicht alternatief voor enums.
Const Assertions met Arrays
U kunt een array van stringliteralen maken en vervolgens een const-assertie gebruiken om het type onveranderlijk te maken en de elementen letterlijke typen te geven.
const statusArray = ["PENDING", "PROCESSING", "COMPLETED"] as const;
type StatusType = typeof statusArray[number];
let currentStatus: StatusType = "PROCESSING";
// currentStatus = "FAILED"; // Fout: Type '"FAILED"' is niet toewijsbaar aan type 'StatusType'.
function processStatus(status: StatusType) {
console.log(`Processing status: ${status}`);
}
processStatus("COMPLETED");
Laten we ontleden wat hier gebeurt:
as const: Deze assertie vertelt TypeScript om de array als alleen-lezen te behandelen en de meest specifieke letterlijke typen voor de elementen te infereren. Dus in plaats van `string[]`, wordt het type `readonly ["PENDING", "PROCESSING", "COMPLETED"]`.typeof statusArray[number]: Dit is een mapped type. Het itereert over alle indexen van destatusArrayen extraheert hun letterlijke typen. Denumberindexsignatuur zegt in wezen: "geef me het type van elk element in deze array". Het resultaat is een union type:"PENDING" | "PROCESSING" | "COMPLETED".
Deze aanpak biedt typeveiligheid vergelijkbaar met string enums, maar genereert minimale JavaScript. De statusArray zelf blijft een array van strings in JavaScript.
Const Assertions met Objecten
Const assertions zijn nog krachtiger wanneer ze worden toegepast op objecten. U kunt een object definiƫren waarbij sleutels uw benoemde constanten vertegenwoordigen en waarden de letterlijke strings of getallen zijn.
const userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
} as const;
type UserRole = typeof userRoles[keyof typeof userRoles];
let currentUserRole: UserRole = "EDITOR";
// currentUserRole = "GUEST"; // Fout: Type '"GUEST"' is niet toewijsbaar aan type 'UserRole'.
function displayRole(role: UserRole) {
console.log(`User role is: ${role}`);
}
displayRole(userRoles.Admin); // Geldig
displayRole("EDITOR"); // Geldig
In dit objectvoorbeeld:
as const: Deze assertie maakt het hele object alleen-lezen. Belangrijker nog, het inferereert letterlijke typen voor alle eigenschapswaarden (bijv."ADMIN"in plaats vanstring) en maakt de eigenschappen zelf alleen-lezen.keyof typeof userRoles: Deze expressie resulteert in een unie van de sleutels van hetuserRolesobject, wat"Admin" | "Editor" | "Viewer"is.typeof userRoles[keyof typeof userRoles]: Dit is een lookup type. Het neemt de unie van sleutels en gebruikt deze om de bijbehorende waarden in hetuserRolestype op te zoeken. Dit resulteert in de unie van de waarden:"ADMIN" | "EDITOR" | "VIEWER", wat ons gewenste type voor rollen is.
De JavaScript-uitvoer voor userRoles zal een gewoon JavaScript-object zijn:
var userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
};
Dit is aanzienlijk lichter dan een typische enum.
Wanneer Const Assertions te Gebruiken
- Alleen-lezen constanten: Wanneer u een vaste set string- of numerieke literalen nodig heeft die tijdens runtime niet mogen veranderen.
- Minimale JavaScript-uitvoer: Als u zich zorgen maakt over de bundelgrootte en de meest performante runtime-representatie voor uw constanten wilt.
- Object-achtige structuur: Wanneer u de leesbaarheid van key-value paren verkiest, vergelijkbaar met hoe u gegevens of configuratie zou structureren.
- Op strings gebaseerde sets: Bijzonder nuttig voor het representeren van statussen, typen of categorieën die het best worden geïdentificeerd door beschrijvende strings.
Alternatief 2: Union Types
Union types stellen u in staat te verklaren dat een variabele een waarde van een van meerdere typen kan bevatten. Wanneer gecombineerd met letterlijke typen (string-, nummer-, booleanliteralen), vormen ze een krachtige manier om een set toegestane waarden te definiƫren zonder dat een expliciete constante declaratie voor de set zelf nodig is.
Union Types met String Literalen
U kunt direct een unie van stringliteralen definiƫren.
type TrafficLightColor = "RED" | "YELLOW" | "GREEN";
let currentLight: TrafficLightColor = "YELLOW";
// currentLight = "BLUE"; // Fout: Type '"BLUE"' is niet toewijsbaar aan type 'TrafficLightColor'.
function changeLight(color: TrafficLightColor) {
console.log(`Changing light to: ${color}`);
}
changeLight("RED");
// changeLight("REDDY"); // Fout
Dit is de meest directe en vaak de meest beknopte manier om een set toegestane stringwaarden te definiƫren.
Union Types met Numerieke Literalen
Op dezelfde manier kunt u numerieke literalen gebruiken.
type HttpStatusCode = 200 | 400 | 404 | 500;
let responseCode: HttpStatusCode = 404;
// responseCode = 201; // Fout: Type '201' is niet toewijsbaar aan type 'HttpStatusCode'.
function handleResponse(code: HttpStatusCode) {
if (code === 200) {
console.log("Success!");
} else {
console.log(`Error code: ${code}`);
}
}
handleResponse(500);
Wanneer Union Types te Gebruiken
- Eenvoudige, directe sets: Wanneer de set van toegestane waarden klein en duidelijk is en geen beschrijvende sleutels vereist, behalve de waarden zelf.
- Impliciete constanten: Wanneer u geen benoemde constante voor de set zelf hoeft te raadplegen, maar direct de letterlijke waarden gebruikt.
- Maximale beknoptheid: Voor eenvoudige scenario's waarbij het definiƫren van een speciaal object of array als overkill aanvoelt.
- Functieparameters/returntypes: Uitstekend voor het definiƫren van de exacte set van acceptabele string- of nummerinvoer/uitvoer voor functies.
Vergelijking van Enums, Const Assertions en Union Types
Laten we de belangrijkste verschillen en gebruiksscenario's samenvatten:
Runtime Gedrag
- Enums: Genereren JavaScript-objecten, mogelijk met omgekeerde mappings.
- Const Assertions (Arrays/Objecten): Genereren gewone JavaScript-arrays of -objecten. De type-informatie wordt tijdens runtime verwijderd, maar de datastructuur blijft bestaan.
- Union Types (met literalen): Geen runtime-representatie voor de unie zelf. De waarden zijn slechts literalen. Typecontrole vindt puur plaats tijdens het compileren.
Leesbaarheid en Expressiviteit
- Enums: Hoge leesbaarheid, vooral met beschrijvende namen. Kan omslachtiger zijn.
- Const Assertions (Objecten): Goede leesbaarheid via key-value paren, die configuraties of instellingen nabootsen.
- Const Assertions (Arrays): Minder leesbaar voor het representeren van benoemde constanten, meer voor een geordende lijst van waarden.
- Union Types: Zeer beknopt. Leesbaarheid hangt af van de duidelijkheid van de letterlijke waarden zelf.
Typeveiligheid
- Alle drie de benaderingen bieden sterke typeveiligheid. Ze zorgen ervoor dat alleen geldige, vooraf gedefinieerde waarden kunnen worden toegewezen aan variabelen of doorgegeven aan functies.
Bundle Grootte
- Enums: Over het algemeen het grootst vanwege gegenereerde JavaScript-objecten.
- Const Assertions: Kleiner dan enums, omdat ze gewone datastructuren produceren.
- Union Types: Het kleinst, omdat ze geen specifieke runtime-datastructuur voor het type zelf genereren, alleen vertrouwen op letterlijke waarden.
Gebruiksscenario Matrix
Hier is een snelle gids:
| Functie | TypeScript Enum | Const Assertion (Object) | Const Assertion (Array) | Union Type (Literals) |
|---|---|---|---|---|
| Runtime Uitvoer | JS Object (met omgekeerde mapping) | Gewoon JS Object | Gewoon JS Array | Geen (alleen letterlijke waarden) |
| Leesbaarheid (Benoemde Constanten) | Hoog | Hoog | Gemiddeld | Laag (waarden zijn namen) |
| Bundel Grootte | Grootst | Gemiddeld | Gemiddeld | Kleinste |
| Flexibiliteit | Goed | Goed | Goed | Uitstekend (voor eenvoudige sets) |
| Gangbaar Gebruik | States, Statuscodes, Categorieƫn | Configuratie, Roldefinities, Feature Flags | Geordende lijsten van onveranderlijke waarden | Functieparameters, eenvoudige beperkte waarden |
Praktische Voorbeelden en Best Practices
Voorbeeld 1: API Status Codes Representeren
Enum:
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Pending = "PENDING"
}
function handleApiResponse(status: ApiStatus) {
// ... logica ...
}
Const Assertion (Object):
const apiStatusCodes = {
SUCCESS: "SUCCESS",
ERROR: "ERROR",
PENDING: "PENDING"
} as const;
type ApiStatus = typeof apiStatusCodes[keyof typeof apiStatusCodes];
function handleApiResponse(status: ApiStatus) {
// ... logica ...
}
Union Type:
type ApiStatus = "SUCCESS" | "ERROR" | "PENDING";
function handleApiResponse(status: ApiStatus) {
// ... logica ...
}
Aanbeveling: Voor dit scenario is een union type vaak het meest beknopte en efficiƫnte. De letterlijke waarden zelf zijn voldoende beschrijvend. Als u aanvullende metadata aan elke status zou moeten koppelen (bijv. een gebruiksvriendelijk bericht), zou een const assertion object een betere keuze zijn.
Voorbeeld 2: Gebruikersrollen Definiƫren
Enum:
enum UserRoleEnum {
Admin = "ADMIN",
Moderator = "MODERATOR",
User = "USER"
}
function getUserPermissions(role: UserRoleEnum) {
// ... logica ...
}
Const Assertion (Object):
const userRolesObject = {
Admin: "ADMIN",
Moderator: "MODERATOR",
User: "USER"
} as const;
type UserRole = typeof userRolesObject[keyof typeof userRolesObject];
function getUserPermissions(role: UserRole) {
// ... logica ...
}
Union Type:
type UserRole = "ADMIN" | "MODERATOR" | "USER";
function getUserPermissions(role: UserRole) {
// ... logica ...
}
Aanbeveling: Een const assertion object biedt hier een goed evenwicht. Het biedt duidelijke key-value paren (bijv. userRolesObject.Admin) die de leesbaarheid kunnen verbeteren bij het verwijzen naar rollen, terwijl het nog steeds performant is. Een union type is ook een zeer sterke kandidaat als directe stringliteralen volstaan.
Voorbeeld 3: Configuratie-opties Representeren
Stel u een configuratieobject voor een globale applicatie voor dat verschillende thema's kan hebben.
Enum:
enum Theme {
Light = "light",
Dark = "dark",
System = "system"
}
interface AppConfig {
theme: Theme;
// ... andere config opties ...
}
Const Assertion (Object):
const themes = {
Light: "light",
Dark: "dark",
System: "system"
} as const;
type Theme = typeof themes[keyof typeof themes];
interface AppConfig {
theme: Theme;
// ... andere config opties ...
}
Union Type:
type Theme = "light" | "dark" | "system";
interface AppConfig {
theme: Theme;
// ... andere config opties ...
}
Aanbeveling: Voor configuratie-instellingen zoals thema's is het const assertion object vaak ideaal. Het definieert duidelijk de beschikbare opties en hun bijbehorende stringwaarden. De sleutels (Light, Dark, System) zijn beschrijvend en koppelen direct aan de waarden, waardoor de configuratiecode zeer begrijpelijk is.
Het Juiste Gereedschap Kiezen voor de Taak
De beslissing tussen TypeScript enums, const assertions en union types is niet altijd zwart-wit. Het komt vaak neer op een afweging tussen runtimeprestaties, bundelgrootte en code leesbaarheid/expressiviteit.
- Kies voor Union Types wanneer u een eenvoudige, beperkte set van string- of numerieke literalen nodig heeft en maximale beknoptheid gewenst is. Ze zijn uitstekend voor functiehandtekeningen en basiswaardbeperkingen.
- Kies voor Const Assertions (met Objecten) wanneer u een meer gestructureerde, leesbare manier wilt om benoemde constanten te definiƫren, vergelijkbaar met een enum, maar met aanzienlijk minder runtime overhead. Dit is geweldig voor configuratie, rollen of elke set waarbij de sleutels aanzienlijke betekenis toevoegen.
- Kies voor Const Assertions (met Arrays) wanneer u simpelweg een onveranderlijke geordende lijst van waarden nodig heeft, en directe toegang via index belangrijker is dan benoemde sleutels.
- Overweeg TypeScript Enums wanneer u hun specifieke functies nodig heeft, zoals omgekeerde mapping (hoewel dit minder gebruikelijk is in moderne ontwikkeling) of als uw team een sterke voorkeur heeft en de prestatie-impact verwaarloosbaar is voor uw project.
In veel moderne TypeScript-projecten zult u een neiging zien naar const assertions en union types boven traditionele enums, vooral voor op strings gebaseerde constanten, vanwege hun betere prestatiekenmerken en vaak eenvoudigere JavaScript-uitvoer.
Globale Overwegingen
Bij het ontwikkelen van applicaties voor een wereldwijd publiek zijn consistente en voorspelbare constante definities cruciaal. De keuzes die we hebben besproken (enums, const assertions, union types) dragen allemaal bij aan deze consistentie door typeveiligheid te garanderen in verschillende omgevingen en ontwikkelaarslocaties.
- Consistentie: Ongeacht de gekozen methode, is consistentie binnen uw project de sleutel. Als u besluit om const assertion-objecten voor rollen te gebruiken, blijf dan dat patroon volgen in de hele codebase.
- Internationalisering (i18n): Bij het definiëren van labels of berichten die geïnternationaliseerd zullen worden, gebruikt u deze typeveilige structuren om ervoor te zorgen dat alleen geldige sleutels of identifiers worden gebruikt. De daadwerkelijke vertaalde strings worden apart beheerd via i18n-bibliotheken. Bijvoorbeeld, als u een `status`-veld heeft dat "PENDING", "PROCESSING", "COMPLETED" kan zijn, zou uw i18n-bibliotheek deze interne identifiers mappen naar gelokaliseerde weergavetekst.
- Tijdzones & Valuta: Hoewel niet direct gerelateerd aan enums, onthoud dat bij het omgaan met waarden zoals datums, tijden of valuta's, TypeScript's typesysteem kan helpen bij het afdwingen van correct gebruik, maar externe bibliotheken zijn meestal nodig voor nauwkeurige globale afhandeling. Een `Currency` union type zou bijvoorbeeld kunnen worden gedefinieerd als `"USD" | "EUR" | "GBP"`, maar de daadwerkelijke conversielogica vereist gespecialiseerde tools.
Conclusie
TypeScript biedt een rijke set tools voor het beheren van constanten. Hoewel enums ons goed hebben gediend, bieden const assertions en union types overtuigende, vaak performantere, alternatieven. Door hun verschillen te begrijpen en de juiste aanpak te kiezen op basis van uw specifieke behoeften - of het nu gaat om prestaties, leesbaarheid of beknoptheid - kunt u robuustere, onderhoudbare en efficiƫntere TypeScript-code schrijven die wereldwijd schaalt.
Het omarmen van deze alternatieven kan leiden tot kleinere bundelgroottes, snellere applicaties en een voorspelbaardere ontwikkelaarservaring voor uw internationale team.